/**
 * 
 */
package org.puresilk.android.gps;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;

import org.puresilk.android.util.Logger;
/**
 * Background GPS Location Service that
 * wakes up at an interval (Default = 5 min)
 * to update current location information.
 * 
 * @author david
 *
 */
public class LocationService extends Service {
	private static final Logger logger = Logger.getInstance("Location Service");
		
	private Location mostCurrentLoc = null;
	private LocationListener gpsListener = null;
	private final ILocService mBinder = new LocBinder();
	private boolean isMonitorRunning = false;
	private static final int TWO_MINUTES = 1000 * 60 * 2;
	private static final int FIVE_MINUTES = 1000 * 60 * 5;
	private int snoozeTime = FIVE_MINUTES;
	
	//-------------------------------
	// Service implementation
	//-------------------------------
	@Override
	public void onCreate() {
		super.onCreate();
		logger.info("~~ Creating LocationService");
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		super.onStart(intent, startId);
		logger.info("~~ Starting Location Monitor");
		// kick it 
		startLocationMonitor();
		return Service.START_STICKY;
	}

	/* (non-Javadoc)
	 * @see android.app.Service#onBind(android.content.Intent)
	 */
	
	public class LocBinder extends Binder implements ILocService{
		/**
		 * Set the snooze alarm time and 
		 * return the previous time.
		 * 
		 * @param delay
		 * @return
		 */
		public int setSnooze(int delay){
			int x = snoozeTime;
			snoozeTime = delay;
			return x;	
		}
		
		/**
		 * Fetch the location information
		 * @return
		 */
		public Location getLocation(){
			return mostCurrentLoc;
		}
	}
	
	
	@Override
	public IBinder onBind(Intent arg0) {
		
		return mBinder; 
	}
	
	//----------------------------------------------
	// Location Monitor
	//----------------------------------------------	
	/**
	 * start up location monitor
	 */
	protected void startLocationMonitor() {
		logger.info("~~!~~ Starting LocationMonitor");
		
		if(isMonitorRunning) {
 			// already running
			return;
		}
		
		// Acquire a reference to the system Location Manager
		LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

		// set up last pos as starting point
		Location loc = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
		if (loc==null) 
			loc = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
		
		if (loc != null) {
			logger.info("~~!~~ Checking last location ");
			if (isBetterLocation(loc))
				mostCurrentLoc = loc;
				
			logger.info(String.format("~~!~~ Last location set: %f %f", mostCurrentLoc.getLatitude(), mostCurrentLoc.getLongitude()));
		}
		else
			if (mostCurrentLoc != null)
				logger.info(String.format("~~!~~ Last location not found", mostCurrentLoc.getLatitude(), mostCurrentLoc.getLongitude()));
			else
				logger.info("~~!~~ No Location information available at this time");
		
		// Define a listener that responds to location updates
		gpsListener = new LocationListener() {
		    public void onLocationChanged(Location location) {
		      // Called when a new location is found by the network location provider.
		     setNewLocation(location);
		    }
		    public void onStatusChanged(String provider, int status, Bundle extras) {
		    	logger.info("~~!~~ OnStatusChanged: " + provider);
		    }

		    public void onProviderEnabled(String provider) {
		    	logger.info("~~!~~ OnProviderEnabled: " + provider);
		    }

		    public void onProviderDisabled(String provider) {
		    	logger.info("~~!~~ OnProviderDisabled: provider");
		    }
		  };

		// Register the listener with the Location Manager to receive location updates
		locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, gpsListener);	
		locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, gpsListener);	
		
		isMonitorRunning = true;
		
		logger.info("~~!~~ LocationManager started....");
	}

	/**
	 * stop the location monitor
	 */
	protected void stopLocationMonitor(){
		if(!isMonitorRunning)
			// not running
			return;
		
		// get manager
		LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);

		// stop the noise
		locationManager.removeUpdates(gpsListener);
		
		// turn off run flag
		isMonitorRunning = false;
		
		// create alarm clock
		Thread sleeper = new Thread() {
			@Override
			public void run() {
				Looper.prepare();
				
				// wait awhile
				try {
					sleep(snoozeTime);
				}
				catch(Exception ex){
					logger.error("!! Snooze failure",ex);
				}
				
				// and then restart 
				startLocationMonitor();
			}
		};
		
		// set the snooze alarm
		sleeper.start();
	}
	
	/**
	 * Set new location when received
	 * @param loc
	 */
	protected void setNewLocation(Location loc) {
		logger.info("~~!~~ New Location info received: " + loc.getProvider());
		if (isBetterLocation(loc)) {
			// save location update
			mostCurrentLoc = loc;

			logger.info(String.format("~~!~~ Location changed: %s (%f, %f)", mostCurrentLoc.getProvider(), mostCurrentLoc.getLatitude(), mostCurrentLoc.getLongitude()));
			
			// turn off updates
			stopLocationMonitor();
		}
	}
	
	/** Determines whether one Location reading is better than the current Location fix
	  * @param location  The new Location that you want to evaluate
	  * @param currentBestLocation  The current Location fix, to which you want to compare the new one
	  */
	protected boolean isBetterLocation(Location loc) {
	    // Check whether the new location fix is newer or older
		if (loc == null) {
			// null is never better....
			return false;
		}
		if (mostCurrentLoc == null) {
			return true;
		}
		
		logger.info("~~!~~ New location check: " + loc.getProvider());
		
	    long timeDelta = loc.getTime() - mostCurrentLoc.getTime();
	    boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
	    boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
	    boolean isNewer = timeDelta > 0;

	    // If it's been more than two minutes since the current location, use the new location
	    // because the user has likely moved
	    if (isSignificantlyNewer) {
	        return true;
	    // If the new location is more than two minutes older, it must be worse
	    } else if (isSignificantlyOlder) {
	        return false;
	    }

	    // Check whether the new location fix is more or less accurate
	    int accuracyDelta = (int) (loc.getAccuracy() - mostCurrentLoc.getAccuracy());
	    boolean isLessAccurate = accuracyDelta > 0;
	    boolean isMoreAccurate = accuracyDelta < 0;
	    boolean isSignificantlyLessAccurate = accuracyDelta > 200;

	    // Check if the old and new location are from the same provider
	    boolean isFromSameProvider = isSameProvider(loc.getProvider(),
	            mostCurrentLoc.getProvider());

	    // Determine location quality using a combination of timeliness and accuracy
	    if (isMoreAccurate) {
	        return true;
	    } else if (isNewer && !isLessAccurate) {
	        return true;
	    } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
	        return true;
	    }
	    return false;
	}

	/** Checks whether two providers are the same */
	private boolean isSameProvider(String provider1, String provider2) {
	    if (provider1 == null) {
	      return provider2 == null;
	    }
	    return provider1.equals(provider2);
	}
}
